﻿using System;
using System.IO;
using Ela.Parsing;
using ElaConsole.Options;
using Ela;
using System.Collections.Generic;
using Ela.Debug;

namespace ElaConsole
{
    internal sealed class MessageHelper
    {
        #region Construction
        private ElaOptions opt;

        internal MessageHelper(ElaOptions opt)
        {
            this.opt = opt;
        }
        #endregion


        #region Methods
        internal bool ValidateOptions()
        {
            if (opt.ShowSymbols != SymTables.None)
                opt.Debug = true;

            if (opt.Silent && opt.ShowEil)
                return IncompatibleOptions("-silent", "-eil");
            else if (opt.Silent && opt.ShowHelp)
                return IncompatibleOptions("-silent", "-help");
            else if (opt.ShowTime != 0 && String.IsNullOrEmpty(opt.FileName))
                return NotInteractiveOption("-time");
            else if (opt.LunchInteractive && String.IsNullOrEmpty(opt.FileName))
                return RequireFileOption("-inter");
            else if (opt.Compile && String.IsNullOrEmpty(opt.FileName))
                return RequireFileOption("-compile");
            else if (opt.Compile && opt.LunchInteractive)
                return IncompatibleOptions("-compile", "-inter");
            else if (opt.WarningsAsErrors && opt.NoWarnings)
                return IncompatibleOptions("-warnaserr", "-nowarn");
            else if (opt.LinkerWarningsAsErrors && opt.LinkerNoWarnings)
                return IncompatibleOptions("-linkWarnaserr", "-linkNowarn");
            
            return true;
        }


        private bool IncompatibleOptions(params string[] names)
        {
            PrintErrorAlways("Unable to use the following options at the same time: {0}.", String.Join(",", names));
            return false;
        }


        private bool RequireFileOption(string name)
        {
            PrintErrorAlways("Unable to use {0} option when a file name is not specified.", name);
            return false;
        }


        private bool NotInteractiveOption(string name)
        {
            PrintErrorAlways("Unable to use {0} option in interactive mode.", name);
            return false;
        }
        

        internal void PrintLogo()
        {
            if (!opt.NoLogo && !opt.Silent)
            {
                var vr = String.Empty;
                var mono = Type.GetType("Mono.Runtime") != null;
                var rt = mono ? "Mono" : "CLR";

                if (mono)
                {
                    var type = Type.GetType("Consts");
                    var fi = default(System.Reflection.FieldInfo);

                    if (type != null && (fi = type.GetField("MonoVersion")) != null)
                        vr = fi.GetValue(null).ToString();
                    else
                        vr = Environment.Version.ToString();
                }
                else
                    vr = Environment.Version.ToString();

                var bit = IntPtr.Size == 4 ? "32" : "64";
                Console.WriteLine("Ela version {0}", ElaVersionInfo.Version);
                Console.WriteLine("Running {0} {1} {2}-bit ({3})", rt, vr, bit, Environment.OSVersion);
                Console.WriteLine();
            }
        }


        internal void PrintInteractiveModeLogo()
        {
            if (!opt.NoLogo && !opt.Silent)
            {
                Console.WriteLine("Interactive mode");

                if (opt.Multiline)
                {
                    Console.WriteLine("Enter expressions in several lines. Put a double semicolon ;; after");
                    Console.WriteLine("an expression to execute it.");
                }
                else
                    Console.WriteLine("Enter expressions and press <Return> to execute.");

                Console.WriteLine();
            }

        }


        internal void PrintPrompt()
        {
            if (!opt.Silent)
            {
                Console.WriteLine();
                var prompt = String.IsNullOrEmpty(opt.Prompt) ? "ela" : opt.Prompt;
                Console.Write(prompt + ">" + (opt.NewLinePrompt?Environment.NewLine:""));
            }
        }


        internal void PrintSecondaryPrompt()
        {
            if (!opt.Silent)
            {
                var promptLength = String.IsNullOrEmpty(opt.Prompt) ? 3 : opt.Prompt.Length;
                Console.Write(new String('-', promptLength) + ">" + (opt.NewLinePrompt ? Environment.NewLine : ""));
            }
        }


        internal void PrintHelp()
        {
            using (var sr = new StreamReader(typeof(MessageHelper).Assembly.
                GetManifestResourceStream("ElaConsole.Properties.Help.txt")))
                Console.WriteLine(sr.ReadToEnd()
                    .Replace("%VERSION%", Const.Version)
                    .Replace("%ELA%", ElaVersionInfo.Version.ToString()));
        }


        internal void PrintExecuteFirstTime()
        {
            Console.WriteLine("Executing code first time...");
        }


        internal void PrintExecuteSecondTime()
        {
            Console.WriteLine("Execution finished. Executing second time, measuring time...");
        }


        internal void PrintSymTables(DebugReader gen)
        {
            var prev = false;

            if ((opt.ShowSymbols & SymTables.Lines) == SymTables.Lines)
            {
                Console.WriteLine("Lines:\r\n");
                Console.Write(gen.PrintSymTables(SymTables.Lines));
                prev = true;
            }

            if ((opt.ShowSymbols & SymTables.Scopes) == SymTables.Scopes)
            {
                Console.WriteLine((prev ? "\r\n" : String.Empty) + "Scopes:\r\n");
                Console.Write(gen.PrintSymTables(SymTables.Scopes));
            }

            if ((opt.ShowSymbols & SymTables.Vars) == SymTables.Vars)
            {
                Console.WriteLine((prev ? "\r\n" : String.Empty) + "Variables:\r\n");
                Console.Write(gen.PrintSymTables(SymTables.Vars));
            }

            if ((opt.ShowSymbols & SymTables.Functions) == SymTables.Functions)
            {
                Console.WriteLine((prev ? "\r\n" : String.Empty) + "Functions:\r\n");
                Console.Write(gen.PrintSymTables(SymTables.Functions));
            }
        }


        internal void PrintUnableWriteFile(string file, Exception ex)
        {
            PrintError("Unable to write cast the file {0}. Error: {1}", file, ex.Message);
        }


        internal void PrintInvalidOption(ElaOptionException ex)
        {
            switch (ex.Error)
            {
                case ElaOptionError.InvalidFormat:
                    if (!String.IsNullOrEmpty(ex.Option))
                        PrintErrorAlways("Invalid format for the '{0}' option.", ex.Option);
                    else
                        PrintErrorAlways("Invalid command line format.");
                    break;
                case ElaOptionError.UnknownOption:
                    PrintErrorAlways("Unknown command line option '{0}'.", ex.Option);
                    break;
            }
        }


        internal void PrintErrors(IEnumerable<ElaMessage> errors)
        {
            if (!opt.Silent)
            {
                foreach (var e in errors)
                    WriteMessage(e.ToString(), e.Type);
            }
        }



        internal void PrintInternalError(Exception ex)
        {
            PrintError("Internal error: {0}", ex.Message);
        }


        internal void PrintError(string message, params object[] args)
        {
            if (!opt.Silent)
            {
                if (args != null && args.Length > 0)
                    WriteMessage(String.Format(message, args), MessageType.Error);
                else
                    WriteMessage(message, MessageType.Error);
            }
        }


        internal void PrintErrorAlways(string message, params object[] args)
        {
            WriteMessage(String.Format("Ela Console error: " + message, args), MessageType.Error);
        }


        private void WriteMessage(string msg, MessageType type)
        {
            Console.WriteLine(msg);
        }
        #endregion
    }
}
